package com.alexander.annotation_processor;

import com.alexander.annotation.ARouter;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import static com.alexander.annotation_processor.Constants.APT_PACKAGE;
import static com.alexander.annotation_processor.Constants.AROUTER_ANNOTATION_TYPE;
import static com.alexander.annotation_processor.Constants.AROUTER_PATH_INTERFACE;
import static com.alexander.annotation_processor.Constants.DOT;
import static com.alexander.annotation_processor.Constants.MODULE_NAME;
import static com.alexander.annotation_processor.Constants.PATH_FILE_NAME;
import static com.alexander.annotation_processor.Constants.PATH_METHOD_NAME;
import static com.alexander.annotation_processor.Constants.PATH_PARAMETER_NAME;

/**
 * author : cjw
 * date : 2022/2/8
 * description :
 * 这部分代码是编译时过程的一部分，没有debug的机会，
 * 只能在每个关键阶段多打一些日志来验证了。
 * 任何module只要依赖了当前的annotation_processor模块，此注解处理器就会处理此模块下的代码（即扫描所有class文件）
 */
// AutoService则是固定的写法，加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器，用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件（其内容是
// 处理器的全类名: com.alexander.annotation_processor.ARouterProcessor
@AutoService(Processor.class)
// 需要让注解处理器处理的注解类型
@SupportedAnnotationTypes({AROUTER_ANNOTATION_TYPE})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
// 注解处理器接收的参数（通过在对应的module下的build.gradle中配置进行传递）
@SupportedOptions({MODULE_NAME, APT_PACKAGE})
public class ARouterProcessor extends AbstractProcessor {
    // 操作Element工具类 (类、函数、属性都是Element)
    private Elements elementUtils;
    // type(类信息)工具类，包含用于操作TypeMirror的工具方法
    private Types typeUtils;
    // Messager用来报告错误，警告和其他提示信息(打印日志)
    private Messager messager;
    // 文件生成器 类/资源，Filer用来创建新的源文件，class文件以及辅助文件
    private Filer filer;
    // 子模块名，如：app/order/personal。需要拼接类名时用到（必传）ARouter$$Group$$order
    private String moduleName;
    // 包名，用于存放APT生成的类文件
    private String packageNameForAPT;

    /**
     * 该方法主要用于一些初始化的操作，通过该方法的参数ProcessingEnvironment
     * 可以获取一些列有用的工具类
     *
     * @param processingEnvironment ProcessingEnvironment对象
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        Map<String, String> parameters = processingEnvironment.getOptions();
        if (!Objects.requireNonNull(parameters).isEmpty()) {
            moduleName = parameters.get(MODULE_NAME);
            packageNameForAPT = parameters.get(APT_PACKAGE);
            // 有坑：Diagnostic.Kind.ERROR，异常会自动结束，不像安卓中Log.e
            messager.printMessage(Diagnostic.Kind.NOTE, "moduleName:" + moduleName);
        }

        if (!(moduleName != null && moduleName.length() > 0) &&
                !(packageNameForAPT != null && packageNameForAPT.length() > 0)) {
            throw new RuntimeException("AnnotationProcessor's parameter 'moduleName' or " +
                    "'packageNameForAPT' is empty, 请在所有使用此注解处理器的模块中的build.gradle文件中配置这两个参数");
        }
    }

    //    @Override
//    public Set<String> getSupportedOptions() {
//        Set<String> set = new HashSet<>();
//        set.add(AROUTER_ANNOTATION_TYPE);
//        return set;
//    }

//    @Override
//    public SourceVersion getSupportedSourceVersion() {
//        return SourceVersion.RELEASE_8;
//    }


//    @Override
//    public Set<String> getSupportedOptions() {
//        Set<String> set = new HashSet<>();
//        set.add(MODULE_NAME);
//        set.add(APT_PACKAGE);
//        return set;
//    }

    /**
     * 相当于main函数，开始处理注解
     * 注解处理器的核心方法，处理具体的注解，生成Java文件
     *
     * @param set              使用了支持处理的注解 的节点集合
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解
     * @return 表示后续处理器不会再处理（已经处理完成）
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set != null && !set.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.NOTE, "ARouterProcessor---start---");
            // 暂存路由表
            Map<String, TypeElement> routerTable = new HashMap<>();
            // 所有被注解标注的类
            Set<? extends Element> tmpElements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            for (Element item : tmpElements) {
                // 过滤出activity（目前只处理activity）
                if (isActivity(item.asType())) {
                    // 获取到注解数据
                    ARouter aRouter = item.getAnnotation(ARouter.class);
                    messager.printMessage(Diagnostic.Kind.NOTE, "found activity:" +
                            item.asType().toString() + ", path:" + aRouter.path());
                    if (!routerTable.containsKey(aRouter.path())) {
                        routerTable.put(aRouter.path(), (TypeElement) item);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.WARNING, "ARouter only support activity at present，" +
                            "can not used by：" + item.asType().toString());
                }
            }
            try {
                // 找到了所有使用ARouter注解的activity, 生产路由表的代码
                generateCode(packageNameForAPT, routerTable);
            } catch (IOException exception) {
                exception.printStackTrace();
            }
            return true;
        }
        return false;
    }

    private boolean isActivity(TypeMirror typeMirror) {
        TypeMirror activityMirror = elementUtils.getTypeElement("android.app.Activity").asType();
        return typeUtils.isSubtype(typeMirror, activityMirror);
    }

    /**
     * 生成固定的代码文件
     * @param parent
     * @param map
     * @throws IOException
     * $T: 类型占位符  $N: 变量占位符  $S: 字符串占位符
     */
    private void generateCode(String parent, Map<String, TypeElement> map) throws IOException {
        if (!Objects.requireNonNull(parent).isEmpty() && !Objects.requireNonNull(map).isEmpty()) {
            // 方法返回值的配置
            TypeName methodReturn = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(String.class)
            );
            // 方法配置：public Map<String, RouterBean> loadPath()
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(PATH_METHOD_NAME)
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(methodReturn);
            // 方法内本地变量配置 Map<String, RouterBean> pathMap = new HashMap<>();
            methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(String.class),
                    PATH_PARAMETER_NAME,
                    ClassName.get(HashMap.class));
            // 方法循环写入数据部分
            /*
             * pathMap.put("/order/OrderMainActivity", class)
             */
            for (Map.Entry<String, TypeElement> item : map.entrySet()) {
//                messager.printMessage(Diagnostic.Kind.NOTE, "全类名：" +
//                        item.getValue().asType().toString());
                methodBuilder.addStatement("$N.put($S, $S)",
                        PATH_PARAMETER_NAME,
                        item.getKey(),
                        item.getValue().asType().toString());
            }
            // 遍历写入过后：返回return pathMap
            methodBuilder.addStatement("return $N",
                    PATH_PARAMETER_NAME);
            // 最终生成的类文件名，如ARouter$$Path$$app
            String resultClassName = PATH_FILE_NAME.concat(moduleName);
            messager.printMessage(Diagnostic.Kind.NOTE, "generate class file:" +
                    packageNameForAPT + DOT + resultClassName);
            // 生成的类需要实现的接口(AOP的transform代码中实例化此类的时候需要用到)
            TypeElement interfaceType = elementUtils.getTypeElement(AROUTER_PATH_INTERFACE);
            // 生成类文件，如ARouter$$Path$$app
            JavaFile.builder(packageNameForAPT,
                    // 类名称
                    TypeSpec.classBuilder(resultClassName)
                            // 实现的接口
                            .addSuperinterface(ClassName.get(interfaceType))
                            // 修饰符
                            .addModifiers(Modifier.PUBLIC)
                            // 为类添加方法
                            .addMethod(methodBuilder.build())
                            // 生成代码
                            .build())
                    // 最终写入文件
                    .build().writeTo(filer);
        }
    }
}




